package com.cloudfun.campusshare.device.protocol.command;

import cn.hutool.core.collection.CollectionUtil;
import com.cloudfun.campusshare.util.ByteUtil;
import org.apache.commons.lang.ArrayUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author wangxiyuan
 * @description 液体泵串口命令格式 SOP	LEN	CMD	SESSION_ID	DATA SUM
 * @createDate 2021/5/19 9:32
 **/
public class PumpFrame {
    /**
     * 默认帧头SOP，固定1个字节，ee
     */
    public final static byte DEFAULT_HEAD = ByteUtil.hexStringToByte("ee")[0];

    /**
     * 响应头部可能为66，也可能为ee,66对应 命令下发成功，ee对应 各个泵响应信息
     */
    public final static byte RESPONSE_DEFAULT_HEAD = ByteUtil.hexStringToByte("66")[0];

    /**
     * 单字节。帧头
     */
    private byte head;

    /**
     * 单字节。从CMD到SUM的字节数（含CMD和SUM）
     */
    private byte length;

    /**
     * 单字节，CMD命令字节。
     */
    private byte cmdType;

    /**
     * 服务端会话编号，6个字节，用户服务端识别通讯会话。如果服务端发起的会话设备应答服务端需要带上服务端下发的会话ID。如果设备发起会话则会话ID为“000000”
     */
    private static final byte[] SESSION_ID = new byte[]{0, 0, 0, 0, 0, 0};

    private byte[] sessionId;
    /**
     * 数据字节，不定长。
     */
    private byte[] data;

    /**
     * 单字节，LEN、CMD、SESSION_ID、DATA的异或值
     */
    private byte sum;

    private byte[] partFrameData;

    private byte[] frameData;

    public PumpFrame(byte cmdType,byte[] data){
        this.head = DEFAULT_HEAD;
        // cmdType长度 + 会话编号6个字节长度 + 数据长度 + sum单字节长度
        this.length = (byte)(1 + 6 + data.length + 1);
        // 开始设计构建随机的sessionId,后来发现消息处理并不需要关心sessionId，sessionId暂时保持一致即可
        this.sessionId = SESSION_ID;//buildSessionId();
        this.cmdType = cmdType;
        this.data = data;
        this.frameData = toArray();
    }

    private PumpFrame(byte head, byte length, byte cmdType,byte[] sessionId, byte[] data, byte sum) {
        this.head = head;
        this.length = length;
        this.cmdType = cmdType;
        this.sessionId = sessionId;
        this.data = data;
        this.sum = sum;
        List<byte[]> bytes = new ArrayList<>();
        bytes.add(new byte[]{head,length,cmdType});
        bytes.add(sessionId);
        bytes.add(data);
        bytes.add(new byte[]{sum});
        this.frameData = ByteUtil.merge(bytes);
    }

    /**
     * 计算sum的值
     * @return
     */
    private void calculateSum(){
        // 不包含头部和sum
        partFrameData = new byte[8 + data.length];
        partFrameData[0] = length;
        partFrameData[1] = cmdType;
        System.arraycopy(sessionId,0,partFrameData,2,sessionId.length);
        System.arraycopy(data,0,partFrameData,8,data.length);
        this.sum = ByteUtil.calculateSum(partFrameData, 0, partFrameData.length);
    }

    private byte[] toArray() {
        calculateSum();
        frameData = new byte[10 + data.length];
        frameData[0] = DEFAULT_HEAD;
        System.arraycopy(partFrameData,0,frameData,1,partFrameData.length);
        frameData[frameData.length - 1] = sum;
        return frameData;
    }

    @Override
    public String toString() {
        return ByteUtil.bytesToHexString(this.frameData);
    }

    /**
     * 解析设备回复数据
     * @param data
     * @return
     */
    public static PumpFrame doParse(byte[] data) {
        // 消息头校验
        byte actualHead = ArrayUtils.subarray(data, 0, 1)[0];
        // 响应消息头既不是 66 也不是 ee
        if (DEFAULT_HEAD != actualHead && RESPONSE_DEFAULT_HEAD != actualHead) {
            throw new RuntimeException("invalid frame head:" + ByteUtil.byteToHexString(actualHead));
        }
        /*int position = ;
        if(RESPONSE_DEFAULT_HEAD == actualHead){
            // 当进行复位操作时，响应消息格式为 66....ee....ee...ee...
            for (int i = 1; i < data.length; i++) {
                if(data[i] == DEFAULT_HEAD){
                    position = i - 1;
                    break;
                }
            }
        }*/
        // 校验和计算内容为：帧长和帧体
        byte actualCheckSum = ByteUtil.calculateSum(data, 1, data.length - 1);
        byte expectedCheckSum = data[data.length - 1];
        if (expectedCheckSum != actualCheckSum) {
            throw new RuntimeException("invalid check sum. expected: " + expectedCheckSum + ", actual: " + actualCheckSum);
        }
        byte actualLength = ArrayUtils.subarray(data, 1, 2)[0];
        byte actualCmdType = ArrayUtils.subarray(data, 2, 3)[0];
        byte[] content = ArrayUtils.subarray(data, 9, data.length - 1);
        byte[] sessionId = ArrayUtils.subarray(data, 3, 9);
        return new PumpFrame(actualHead,actualLength,actualCmdType,sessionId,content,actualCheckSum);
    }

    /**
     * 响应消息的格式可能为
     * 1. 66 .....
     * 2. ee .....
     * 3. 66 .....ee......ee......ee......
     * @param data
     * @return
     */
    public static List<PumpFrame> parse(byte[] data){
        List<Integer> frameSegments = new ArrayList<>();
        // 当响应消息为ee ....，不考虑,i从位置1进行遍历
        for (int i = 1; i < data.length; i++) {
            if (DEFAULT_HEAD == data[i]) {
                frameSegments.add(i);
            }
        }
        List<PumpFrame> list = new ArrayList<>();
        // 当响应消息只包含66的头帧或只包含ee头帧
        if(CollectionUtil.isEmpty(frameSegments)){
            list.add(doParse(data));
        }else{
            for (int i = 0; i < frameSegments.size(); i++) {
                int position = frameSegments.get(i);
                byte[] frameData;
                if (i < frameSegments.size() - 1) {
                    frameData = ArrayUtils.subarray(data, position, frameSegments.get(i + 1));
                }else {
                    frameData = ArrayUtils.subarray(data, position, data.length);
                }
                list.add(doParse(frameData));
            }
        }
        return list;
    }

    /**
     * 构建随机的sessionId
     * @return
     */
    private byte[] buildSessionId(){
        byte[] bytes = new byte[6];
        ThreadLocalRandom.current().nextBytes(bytes);
        return bytes;
    }

    /**
     * 获取sessionId的hashCode
     * @return
     */
    public int getSessionIdHashCode(){
        return new String(this.sessionId == null ? SESSION_ID : this.sessionId).hashCode();
    }

    /**
     * 获取命令类型
     * @return
     */
    public byte getCmdType(){
        return this.cmdType;
    }

    public String getCheckSum(){
        return ByteUtil.byteToHexString(this.sum);
    }

    public byte[] getData() {
        return data;
    }

    public static void main(String[] args) {
        // 660B060000000000020101010A 66110600000000000301000101000101000114
        // 660B060000000000000101010CEE0903000000000000010BEE0904000000000000010CEE0905000000000000010D
        byte[] bytes = ByteUtil.hexStringToByte("660B060000000000000101010CEE0903000000000000010BEE0904000000000000010CEE0905000000000000010D");
        /*List<PumpFrame> list = parse(bytes);
        for (PumpFrame pumpFrame : list) {
            System.out.println(pumpFrame.toString());
        }*/
        List list = parse(bytes);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).toString());
        }

        System.out.println(ByteUtil.bytesToHexString(new byte[]{1,1,1,1,1}));
    }

}
